/**
 *Copyright 2013 by dragon.
 *
 *File name: Bencoding.java
 *Author:      dragon
 *Email:       fufulove2012@gmail.com
 *Blog:        http://blog.csdn.net/xidomlove
 *Version:     1.0.0
 *Date:        2013-10-7 下午9:01:24
<<<<<<< HEAD
 *Description: bencode实现,参考文档：http://blog.csdn.net/wangpingfang/article/details/4202089
=======
 *Description: 参考文档：http://blog.csdn.net/wangpingfang/article/details/4202089
>>>>>>> 805f866184b638874b14bdfacf982625a6269ad9
 */
package com.dragon.jaxel.bittorrent.bencode;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;
import java.util.Map.Entry;

/**
 * @author dragon8
 * 
 */
public class Bencode {
	/**
	 * 从文件解析bencode数据
	 * 
	 * @param file
	 *            文件名
	 * @return 解析成功的字典
	 * @throws IOException
	 */
	public static BencodeValue decodeFile(String file) throws IOException {
		BufferedInputStream inputStream = new BufferedInputStream(
				new FileInputStream(file));
		BencodeValue bencodeValue = decode(inputStream);
		inputStream.close();
		return bencodeValue;
	}

	/**
	 * 从字符串解析bencode数据
	 * 
	 * @param content
	 * @return
	 * @throws IOException
	 */
	public static BencodeValue decodeStringContent(String content)
			throws IOException {
		ByteArrayInputStream inputStream = new ByteArrayInputStream(
				content.getBytes("utf-8"));
		BencodeValue bencodeValue = decode(inputStream);
		inputStream.close();
		return bencodeValue;
	}

	/**
	 * 从流中解析bencode数据
	 * 
	 * @param inputStream
	 * @return
	 * @throws IOException
	 */
	public static BencodeValue decodeStream(InputStream inputStream)
			throws IOException {
		return decode(inputStream);
	}

	private static BencodeValue decode(InputStream inputStream)
			throws IOException {
		int b = inputStream.read();
		switch (b) {
		case 'd':
			// 是字典
			return decodeDictionary(inputStream);
		case 'l':
			// 是序列
			return decodeList(inputStream);
		case 'i':
			// 是整数
			return decodeNumber(inputStream);
		default:
			// 是字符串
			if (Character.isDigit(b)) {
				return decodeString(inputStream, b);
			} else {
				throw new BencodeException(
						"Decode Exception:The BitTorrent file is invalid!");
			}
		}
	}

	/**
	 * 读取字典
	 * 
	 * @param inputStream
	 * @return
	 * @throws IOException
	 */
	private static BencodeDictionary decodeDictionary(InputStream inputStream)
			throws IOException {
		BencodeDictionary dictionary = new BencodeDictionary();
		int b;
		while (true) {
			b = inputStream.read();
			if (b == 'e') {
				break;
			}
			String key = decodeString(inputStream, b).getData();
			b = inputStream.read();
			switch (b) {
			case 'd':
				// 是字典
				dictionary.put(key, decodeDictionary(inputStream));
				break;
			case 'l':
				// 是序列
				dictionary.put(key, decodeList(inputStream));
				break;
			case 'i':
				// 是整数
				dictionary.put(key, decodeNumber(inputStream));
				break;
			default:
				// 是字符串
				if (Character.isDigit(b)) {
					dictionary.put(key, decodeString(inputStream, b));
				} else {
					throw new BencodeException(
							"Decode Exception:The BitTorrent file is invalid!");
				}
				break;
			}
		}
		return dictionary;
	}

	/**
	 * 读取序列
	 * 
	 * @param inputStream
	 * @return
	 * @throws IOException
	 */
	private static BencodeList decodeList(InputStream inputStream)
			throws IOException {
		BencodeList list = new BencodeList();
		int b;
		boolean end = false;
		while (!end) {
			b = inputStream.read();
			switch (b) {
			case 'd':
				// 是字典
				list.add(decodeDictionary(inputStream));
				break;
			case 'l':
				// 是序列
				list.add(decodeList(inputStream));
				break;
			case 'i':
				// 是整数
				list.add(decodeNumber(inputStream));
				break;
			case 'e':
				// 结束
				end = true;
				break;
			default:
				// 是字符串
				if (Character.isDigit(b)) {
					list.add(decodeString(inputStream, b));
				} else {
					throw new BencodeException(
							"Decode Exception:The BitTorrent file is invalid!");
				}
				break;
			}
		}
		return list;
	}

	/**
	 * 读取整数
	 * 
	 * @param inputStream
	 * @return
	 * @throws IOException
	 */
	private static BencodeNumber decodeNumber(InputStream inputStream)
			throws IOException {
		ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
		int b;
		while ((b = inputStream.read()) != 'e') {
			outputStream.write(b);
		}
		BencodeNumber number = new BencodeNumber(outputStream.toByteArray());
		outputStream.close();
		return number;
	}

	/**
	 * 读取一个字符串
	 * 
	 * @param inputStream
	 * @param b
	 *            已经读取的一个字节，该字节包含在表示字符串长度的ascii码内
	 * @return
	 * @throws IOException
	 */
	private static BencodeString decodeString(InputStream inputStream, int b)
			throws IOException {
		ByteArrayOutputStream bufferStream = new ByteArrayOutputStream();
		bufferStream.write(b);
		while ((b = inputStream.read()) != ':') {
			bufferStream.write(b);
		}

		// 得到长度
		b = Integer.valueOf(bufferStream.toString());
		bufferStream.close();

		// 读取字符串
		byte[] val = new byte[b];
		int offset = 0;
		while (offset != b) {
			offset += inputStream.read(val, offset, val.length - offset);
		}
		return new BencodeString(val);
	}

	/**
	 * 编码一个bencode对象
	 * 
	 * @param bencodeValue
	 * @return 编码完成的字节数组
	 * @throws IOException
	 */
	public static byte[] encode(BencodeValue bencodeValue) throws IOException {
		ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
		encode(bencodeValue, outputStream);
		byte[] bytes = outputStream.toByteArray();
		outputStream.close();
		return bytes;
	}

	private static void encode(BencodeValue bencodeValue,
			ByteArrayOutputStream outputStream) throws IOException {
		if (bencodeValue instanceof BencodeDictionary) {
			// 编码字典
			outputStream.write('d');

			BencodeDictionary dic = (BencodeDictionary) bencodeValue;
			Iterator<Entry<String, BencodeValue>> iterator = dic.getMap()
					.entrySet().iterator();
			while (iterator.hasNext()) {
				Entry<String, BencodeValue> entry = iterator.next();
				// 写键
				outputStream.write(String.valueOf(entry.getKey().length())
						.getBytes());
				outputStream.write(':');
				outputStream.write(entry.getKey().getBytes());

				encode(entry.getValue(), outputStream);
			}

			outputStream.write('e');
		} else if (bencodeValue instanceof BencodeList) {
			outputStream.write('l');

			BencodeList list = (BencodeList) bencodeValue;
			for (BencodeValue element : list.getList()) {
				encode(element, outputStream);
			}

			outputStream.write('e');
		} else if (bencodeValue instanceof BencodeString) {
			outputStream.write(String.valueOf(bencodeValue.getBytes().length)
					.getBytes());

			outputStream.write(':');

			outputStream.write(bencodeValue.getBytes());
		} else if (bencodeValue instanceof BencodeNumber) {
			outputStream.write('i');

			outputStream.write(bencodeValue.getBytes());

			outputStream.write('e');
		} else {
			throw new BencodeException("Invalid bencode value!");
		}
	}
}